import logging

import numpy as np
from scipy import stats

from loudml import errors
from loudml.misc import parse_timedelta, make_datetime, make_ts
from loudml.model import Model
import matplotlib.pyplot as plt
import pandas as pd
from pandas.plotting import register_matplotlib_converters


class HotellingPrediction:
    """
    Time-series prediction
    """
    def __init__(self, model, timestamps, predicted):
        self.model = model
        self.timestamps = timestamps
        self.predicted = predicted

    def format_buckets(self):
        predict_buckets = []
        for field, results in self.predicted.items():
            for j in range(len(results)):
                bucket = {}
                if "Probability" in field:
                    bucket['timestamp'] = self.timestamps[j]
                    bucket['predicted'] = {field: results[j]}
                else:
                    bucket['timestamp'] = self.timestamps[results[j][0]]
                    bucket['predicted'] = {field: results[j][1]}
                predict_buckets.append(bucket)
        return predict_buckets

class Hotelling(Model):
    TYPE = 'hotelling'

    def __init__(self, settings=None, state=None):
        print("### 创建loudml.Hotelling模型\n")
        if settings == None:
            return
        super().__init__(settings, state)

        # settings = self.validate(settings)
        # self.bucket_interval = parse_timedelta(settings.get('bucket_interval')).total_seconds()
        # self.interval = parse_timedelta(settings.get('interval')).total_seconds()
        # self.offset = parse_timedelta(settings.get('offset')).total_seconds()

    @property
    def type(self):
        return self.TYPE

    @property
    def is_trained(self):
        return True  # 不需要训练

    def convert_to_nparray(self, data):
        if not isinstance(data, np.ndarray):
            return np.array(data)
        else:
            return data

    def detect(self, data, threshold):
        """
        Parameters
        ----------
        data : array_like
               Input array or object that can be converted to an array.
        threshold : float

        Returns
        -------
        List of tuples where each tuple contains index number and anomalous value.
        """
        data = self.convert_to_nparray(data)
        # apply_default value on missing data 
        self.apply_defaults(data)
        # Set the threshold of abnormality
        abn_th = stats.chi2.interval(1 - threshold, 1)[1]
        abn_th1 = stats.chi2.ppf(0.50, 1)
        abn_th2 = stats.chi2.ppf(0.90, 1)
        abn_th3 = stats.chi2.ppf(0.95, 1)

        # Covert raw data into the degree of abnormality
        avg = np.average(data)
        var = np.var(data)
        data_abn = [(x - avg) ** 2 / var for x in data]

        # Abnormality determination
        result = []
        probability = []
        for (index, x) in enumerate(data_abn):
            if x > abn_th:
                result.append((index, data[index]))
            # 判断每个点的异常概率
            if x < abn_th1:
                probability.append(0.5)
            elif x < abn_th2:
                probability.append(0.9)
            elif x < abn_th3:
                probability.append(0.95)
            else:
                probability.append(1.0)
        return result, probability

    def predict(
            self,
            datasource,
            from_date,
            to_date,
            threshold):

        period = self.build_date_range(from_date, to_date)
        logging.info("predict(%s) range=%s", self.name, period)

        # get data
        data = datasource.get_times_data(self, period.from_ts, period.to_ts)

        # 将数据存储方式变为:
        #   dataset['feature_name'] = data[]
        dataset = {}
        timeset = []
        for i, (_, val, timeval) in enumerate(data):
            timeset.append(timeval)
            for j, x in enumerate(val):
                feature = self.features[j]
                if dataset.get(feature.name) == None:
                    dataset[feature.name] = []
                dataset[feature.name].append(x)

        results = {} # 检测异常的点
        num_features = len(self.features)
        for i in range(num_features):
            feature_name = self.features[i].name
            dataT = dataset[feature_name]
            result, probability = self.detect(dataT, threshold)
            print("### ", feature_name, ":", len(result))
            if len(result) > 0:
                results[feature_name] = result
            # 每个点的异常概率也存在results里
            results[feature_name+"_Probability"] = probability

        return HotellingPrediction(self, timeset, results)

    def apply_defaults(self, x):
        """
        Apply default feature value to np array
        """
        feature = self.features[0]
        if feature.default == "previous":
            previous = None
            for j, value in enumerate(x):
                if np.isnan(value):
                    x[j] = previous
                else:
                    previous = x[j]
        if feature.default == "mean":
            x[np.isnan(x)] = np.nanmean(x)
        elif not np.isnan(feature.default):
            x[np.isnan(x)] = feature.default